#!/usr/bin/env python
#-*- coding: UTF-8 -*-
# schapi.py: Programming interface of node manager for scheduler
#
# Copyright (C) 2008-2010 Hily Jiang <hilyjiang@gmail.com>
#
# This file is part of dtask.
#
# This software is provided 'as-is', without any express or implied
# warranty.  In no event will the authors be held liable for any damages
# arising from the use of this software.
#
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
#
# 1. The origin of this software must not be misrepresented; you must not
#    claim that you wrote the original software. If you use this software
#    in a product, an acknowledgment in the product documentation would be
#    appreciated but is not required.
# 2. Altered source versions must be plainly marked as such, and must not be
#    misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution.

"""
Programming interface of node manager for scheduler.
"""

import threading
import socket
import select

from utils import CommandBuffer


CFG_SCH_BUFSIZ = 1024


class Dispatcher(object):
    """Dispatcher, dispatch task and get task result."""
    def __init__(self):
        self.statusEvent = threading.Event()
        self.resultSem = threading.Semaphore(0)
        self.statusSem = threading.Semaphore(0)
        self.dispatchOkEvents = {}
        self.resultInfos = []
        self.statusInfos = []
        self.connected = False
        self.closed = False
        self.threadLock = threading.Semaphore(1)

    def connect(self, nodeServerIP, nodeServerPort, schName='', schKey=''):
        if self.connected: return False
        self.connected = True

        self.schName = schName
        self.schKey = schKey

        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        addr = (nodeServerIP, nodeServerPort)
        try:
            self.sock.connect(addr)
        except:
            print 'connect failed', addr
            self.connected = False
            return False

        # if schName exists send schName and schKey to server
        if self.schName != '':
            self.sock.sendall('PASSPORT %s %s\n' % (self.schName, self.schKey))

        self.rBuf = CommandBuffer()
        self.recvThread = threading.Thread(target=self.__recvThreadFunc)
        self.recvThread.setDaemon(True)
        self.recvThread.start()
        return True

    def close(self):
        """Close connection to node manager."""
        if not self.connected: return False
        self.closed = True
        self.connected = False

    def __recvThreadFunc(self):
        """Thread function to receive data."""
        while True:
            infds, outfds, errfds = select.select([self.sock,], [], [], 5)
            if infds and len(infds) == 0: # timeout
                  if self.closed == True:
                      for ev in self.dispatchOkEvents:
                          self.dispatchOkEvents[ev].set()
                      self.resultInfos.append(None)
                      self.statusInfos.append(None)
                      self.resultSem.release()
                      self.statusSem.release()
                      break
                  else:
                      continue
            data = self.sock.recv(CFG_SCH_BUFSIZ)
            if data == '':
                for ev in self.dispatchOkEvents:
                    self.dispatchOkEvents[ev].set()
                self.resultInfos.append(None)
                self.statusInfos.append(None)
                self.resultSem.release()
                self.statusSem.release()
                break
            self.rBuf.addData(data)
            self.threadLock.acquire()
            while True:
                cmdInfo = self.rBuf.getCommand()
                if cmdInfo == None: break
                cmd, params = cmdInfo
                if cmd == 'RESULT':
                    try:
                        ps = params.split(None, 1)
                        if len(ps) == 2:
                            taskId, result = ps
                        else:
                            taskId = ps[0]
                            result = ''
                        self.resultInfos.append((taskId, result))
                    except Exception:
                        self.resultInfos.append(None)
                    self.resultSem.release()
                elif cmd == 'STATUS':
                    try:
                        taskId, status = params.split(None, 1)
                        taskId = int(taskId)
                        self.statusInfos.append((taskId, status))
                        self.dispatchOkEvents[taskId].set()
                        self.statusSem.release()
                    except Exception, e:
                        print e
                        self.statusInfos.append(None)
            self.threadLock.release()

    def dispatchTask(self, taskId, taskType, taskParams):
        """Dispatch a task to node server."""
        self.threadLock.acquire()
        taskId = int(taskId)
        self.sock.send('TASK %d %s %s\n' % (taskId, taskType, taskParams))
        self.dispatchOkEvents[taskId] = threading.Event()
        dispatchOkEvent = self.dispatchOkEvents[taskId]
        self.threadLock.release()
        dispatchOkEvent.wait()
        del self.dispatchOkEvents[taskId]

    def recvResult(self):
        """Receive result from node server."""
        self.resultSem.acquire()
        resultInfo = self.resultInfos[0]
        del self.resultInfos[0]
        return resultInfo

    def recvStatus(self):
        """Receive status from node server."""
        self.statusSem.acquire()
        statusInfo = self.statusInfos[0]
        del self.statusInfos[0]
        return statusInfo


def test():
    dp = Dispatcher()
    if not dp.connect('127.0.0.1', 8809, 'testsch'):
        print 'connect failed'
    else:
        dp.dispatchTask(1, 'lrcdown', 'Lene Marlin - Love')


if __name__ == '__main__':
    test()
